package com.extsci.webmail.web.controller;

import com.extsci.webmail.common.entity.fileupload.MultipartUploadParser;
import com.extsci.webmail.common.entity.fileupload.RequestParser;
import com.extsci.webmail.common.entity.http.FileResponse;
import com.extsci.webmail.common.util.MyFileUtils;
import com.yanchang.auth.client.annotation.IgnoreClientToken;
import com.yanchang.auth.client.annotation.IgnoreUserToken;
import com.yanchang.common.context.BaseContextHandler;
import lombok.extern.log4j.Log4j;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.regex.Pattern;

@Controller
@Log4j
public class FileController
{
    @Value("${webmail.file.path}")
    private String filePath;

    @Value("${webmail.file.url}")
    private String fileUrl;

    private static String CONTENT_LENGTH = "Content-Length";
    private static int SUCCESS_RESPONSE_CODE = 200;


    @Autowired
    private ServletContext context;

    @DeleteMapping("/file")
    public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException
    {
        String uuid = req.getPathInfo().replaceAll("/", "");

        handleDeleteFileRequest(uuid, resp);
    }

    private void handleDeleteFileRequest(String uuid, HttpServletResponse resp) throws IOException
    {
        File uploadDir = new File(MyFileUtils.contactUri(filePath, getUploadUriByUserName(BaseContextHandler.getUsername())), uuid);
        System.out.println("handleDeleteFileRequest:" + uploadDir.getAbsolutePath());
        FileUtils.deleteDirectory(uploadDir);

        if (uploadDir.exists())
        {
            log.warn("couldn't find or delete " + uuid);
        }
        else
        {
            log.info("deleted " + uuid);
        }

        resp.setStatus(SUCCESS_RESPONSE_CODE);
    }

    @IgnoreClientToken
    @IgnoreUserToken
    @GetMapping("/resource/**")
    public ResponseEntity<byte[]> getFile(HttpServletRequest request){
        String uri = null;
        try {
            uri = URLDecoder.decode(request.getRequestURI(), "utf-8");
            // 根据uri获取文件真实路径
            String realFilePath = MyFileUtils.contactUri(filePath, uri);
            System.out.println(realFilePath);
            // 判断文件是否存在
            File file = new File(realFilePath);
            if (file.exists()){
                HttpHeaders headers = new HttpHeaders();
                String fileName = URLEncoder.encode(file.getName(),"utf-8"); //解决中文文件名下载后乱码的问题
                headers.setContentDispositionFormData("attachment", fileName);
                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                return new ResponseEntity<>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    @PostMapping("/file/upload")
    public @ResponseBody
    FileResponse doPost(HttpServletRequest req, HttpServletResponse resp)
    {
        RequestParser requestParser = null;
        String tempDirPath = MyFileUtils.contactUri(
                filePath,
                MyFileUtils.FILE_SEPARATOR,
                MyFileUtils.TEMP
                );
        File tempDir = new File(tempDirPath);
        try
        {

            if (ServletFileUpload.isMultipartContent(req))
            {

                MultipartUploadParser multipartUploadParser = new MultipartUploadParser(req, tempDir, context);
                requestParser = RequestParser.getInstance(req, multipartUploadParser);
                writeFileForMultipartRequest(requestParser);
                if (requestParser.getTotalParts()-1 != requestParser.getPartIndex()){
                    if (requestParser.generateError()){
                        return new FileResponse("Generated error",false);
                    }else {
                        return new FileResponse(true,"", "");
                    }

                }else {
                    // 上传成功
                    // 获取url
                    String url = MyFileUtils.contactUri(
                            fileUrl,
                            getUploadUriByUserName(BaseContextHandler.getUsername()),
                            MyFileUtils.FILE_SEPARATOR,
                            requestParser.getUuid(),
                            MyFileUtils.FILE_SEPARATOR,
                            requestParser.getOriginalFilename()
                            );
                    return new FileResponse(true,requestParser.getOriginalFilename(), url);
                }
            }
            else
            {
                requestParser = RequestParser.getInstance(req, null);

                if (requestParser.getMethod() != null
                        && requestParser.getMethod().equalsIgnoreCase("DELETE"))
                {
                    String uuid = requestParser.getUuid();
                    handleDeleteFileRequest(uuid, resp);
                }
                else
                {
                    writeFileForNonMultipartRequest(req, requestParser);
                    if (requestParser.generateError()){
                        return new FileResponse("Generated error",false);
                    }else {
                        return new FileResponse(true,"", "");
                    }
                }
            }
        } catch (Exception e)
        {
            log.error("Problem handling upload request", e);
            if (e instanceof MergePartsException)
            {
                return new FileResponse(e.getMessage(),true);
            }
            else
            {
                return new FileResponse(e.getMessage(),false);
            }
        }
        return null;
    }

    private void writeFileForNonMultipartRequest(HttpServletRequest req, RequestParser requestParser) throws Exception
    {
        File dir = new File(MyFileUtils.contactUri(filePath, getUploadUriByUserName(BaseContextHandler.getUsername())), requestParser.getUuid());
        String contentLengthHeader = req.getHeader(CONTENT_LENGTH);
        long expectedFileSize = Long.parseLong(contentLengthHeader);

        if (requestParser.getPartIndex() >= 0)
        {
            writeFile(req.getInputStream(), new File(dir, requestParser.getUuid() + "_" + String.format("%05d", requestParser.getPartIndex())), null);

            if (requestParser.getTotalParts()-1 == requestParser.getPartIndex())
            {
                File[] parts = getPartitionFiles(dir, requestParser.getUuid());
                File outputFile = new File(dir, requestParser.getFilename());
                for (File part : parts)
                {
                    mergeFiles(outputFile, part);
                }

                assertCombinedFileIsVaid(requestParser.getTotalFileSize(), outputFile, requestParser.getUuid());
                deletePartitionFiles(dir, requestParser.getUuid());
            }
        }
        else
        {
            writeFile(req.getInputStream(), new File(dir, requestParser.getFilename()), expectedFileSize);
        }
    }


    private void writeFileForMultipartRequest(RequestParser requestParser) throws Exception
    {
        File dir = new File(MyFileUtils.contactUri(filePath, getUploadUriByUserName(BaseContextHandler.getUsername())), requestParser.getUuid());

        dir.mkdirs();
        if (requestParser.getPartIndex() >= 0)
        {
            writeFile(requestParser.getUploadItem().getInputStream(), new File(dir, requestParser.getUuid() + "_" + String.format("%05d", requestParser.getPartIndex())), null);

            if (requestParser.getTotalParts()-1 == requestParser.getPartIndex())
            {

                File[] parts = getPartitionFiles(dir, requestParser.getUuid());
                File outputFile = new File(dir, requestParser.getOriginalFilename());
                for (File part : parts)
                {
                    mergeFiles(outputFile, part);
                }

                //验证文件是否保存正确
                assertCombinedFileIsVaid(requestParser.getTotalFileSize(), outputFile, requestParser.getUuid());
                //删除分区文件
                deletePartitionFiles(dir, requestParser.getUuid());
            }
        }
        else
        {
            writeFile(requestParser.getUploadItem().getInputStream(), new File(dir, requestParser.getFilename()), null);
        }
    }

    private void assertCombinedFileIsVaid(long totalFileSize, File outputFile, String uuid) throws MergePartsException
    {
        if (totalFileSize != outputFile.length())
        {
            deletePartitionFiles(new File(MyFileUtils.contactUri(filePath, getUploadUriByUserName(BaseContextHandler.getUsername()))), uuid);
            outputFile.delete();
            throw new MergePartsException("Incorrect combined file size!");
        }

    }


    private static class PartitionFilesFilter implements FilenameFilter
    {
        private String filename;
        PartitionFilesFilter(String filename)
        {
            this.filename = filename;
        }

        @Override
        public boolean accept(File file, String s)
        {
            return s.matches(Pattern.quote(filename) + "_\\d+");
        }
    }

    private static File[] getPartitionFiles(File directory, String filename)
    {
        File[] files = directory.listFiles(new PartitionFilesFilter(filename));
        Arrays.sort(files);
        return files;
    }

    private static void deletePartitionFiles(File directory, String filename)
    {
        File[] partFiles = getPartitionFiles(directory, filename);
        for (File partFile : partFiles)
        {
            partFile.delete();
        }
    }

    private File mergeFiles(File outputFile, File partFile) throws IOException
    {
        FileOutputStream fos = new FileOutputStream(outputFile, true);

        try
        {
            FileInputStream fis = new FileInputStream(partFile);

            try
            {
                IOUtils.copy(fis, fos);
            }
            finally
            {
                IOUtils.closeQuietly(fis);
            }
        }
        finally
        {
            IOUtils.closeQuietly(fos);
        }

        return outputFile;
    }

    private File writeFile(InputStream in, File out, Long expectedFileSize) throws IOException
    {
        FileOutputStream fos = null;

        try
        {
            fos = new FileOutputStream(out);

            IOUtils.copy(in, fos);

            if (expectedFileSize != null)
            {
                Long bytesWrittenToDisk = out.length();
                if (!expectedFileSize.equals(bytesWrittenToDisk))
                {
                    log.warn("Expected file {} to be {} bytes; file on disk is {} bytes" + new Object[] { out.getAbsolutePath(), expectedFileSize, 1 });
                    out.delete();
                    throw new IOException(String.format("Unexpected file size mismatch. Actual bytes %s. Expected bytes %s.", bytesWrittenToDisk, expectedFileSize));
                }
            }

            return out;
        }
        catch (Exception e)
        {
            throw new IOException(e);
        }
        finally
        {
            IOUtils.closeQuietly(fos);
        }
    }

    private class MergePartsException extends Exception
    {
        MergePartsException(String message)
        {
            super(message);
        }
    }

    private String getUploadUriByUserName(String username){
        String uri = MyFileUtils.contactUri(
                MyFileUtils.FILE_SEPARATOR,
                MyFileUtils.RESOURCE,
                MyFileUtils.FILE_SEPARATOR,
                username,
                MyFileUtils.FILE_SEPARATOR,
                MyFileUtils.UPLOAD);
        MyFileUtils.createDirs(uri);
        return uri;
    }
}
